package com.ponhu.ea2.stockcenter.provider.service.biz.stockwarehouse;

import com.alibaba.nacos.common.utils.CollectionUtils;
import com.ponhu.ea2.stockcenter.dto.WMSEventInfoDTO;
import com.ponhu.ea2.stockcenter.dto.WMSInventoryInfo;
import com.ponhu.ea2.stockcenter.enums.ExchangeActionTypeEnum;
import com.ponhu.ea2.stockcenter.enums.ResponseCodeEnum;
import com.ponhu.ea2.stockcenter.provider.entity.*;
import com.ponhu.ea2.stockcenter.provider.service.biz.CalcStockDataService;
import com.ponhu.ea2.stockcenter.provider.service.entity.StockInventoryDocOccupyService;
import com.ponhu.ea2.stockcenter.provider.service.entity.StockInventoryRealService;
import com.ponhu.ea2.stockcenter.provider.service.entity.StockInventoryWarehouseTraceService;
import com.ponhu.ea2.stockcenter.provider.support.enums.StockChangeInnerEventEnum;
import com.ponhu.ea2.stockcenter.provider.support.exception.StockRuntimeException;
import com.ponhu.ea2.stockcenter.provider.utils.CommonUtils;
import io.netty.util.concurrent.FastThreadLocal;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;

import static com.ponhu.ea2.stockcenter.enums.ExchangeActionTypeEnum.TRANSFER_APPLY;
import static com.ponhu.ea2.stockcenter.enums.ResponseCodeEnum.*;

@Service
public class AbsUtilHandler {

    @Autowired // 当前请求是否存在销售侧库存占用(销售订单占用或采退单占用)
    protected FastThreadLocal<Boolean> isSaleOccupyLocal;
    @Autowired // 获取请求参数Map(key: 货品编码，value: 仓库变动数量)
    protected FastThreadLocal<Map<String, Integer>> inventoryLocal;
    @Autowired // 库存变动动作配置信息
    private FastThreadLocal<StockWarehouseChangeSettings> settingsLocal;
    @Autowired // 货品仓库库存信息
    protected FastThreadLocal<List<StockWarehouseRelation>> whStocksLocal;
    @Autowired // 当前请求类型对销售侧库存影响的类型枚举
    protected FastThreadLocal<List<StockWarehouseChange>> whChangesLocal;
    @Autowired // 请求部分失败异常
    protected FastThreadLocal<StockRuntimeException> stockRuntimeExceptionLocal;

    @Autowired
    private StockInventoryRealService stockInventoryRealService;
    @Autowired
    protected StockInventoryDocOccupyService stockInventoryDocOccupyService;
    @Autowired
    protected StockInventoryWarehouseTraceService phStockInventoryWarehouseWmsService;

    /**
     * 解析仓库库存动态类型
     *
     * @param stockWarehouseRelations
     */
    public void actionHandler(List<StockWarehouseRelation> stockWarehouseRelations) {

        StockWarehouseChangeSettings settings = settingsLocal.get();

        stockWarehouseRelations.stream().forEach(stock -> {
            // 请求变动库存数
            Integer inventoryQuantity = inventoryLocal.get().get(stock.getInventoryCode());
            stock.setRealStockNum(CalcStockDataService.getOperatorResult(stock.getRealStockNum(),
                            inventoryQuantity, settings.getRealStockAction())) // 实物库存
                    .setAvailableStockNum(CalcStockDataService.getOperatorResult(stock.getAvailableStockNum(),
                            inventoryQuantity, settings.getAvailableStockAction())) // 可用库存
                    .setLockedStockNum(CalcStockDataService.getOperatorResult(stock.getLockedStockNum(),
                            inventoryQuantity, settings.getLockedStockAction())); // 锁定库存
        });
    }

    /**
     * 获取对应货品销售占用或可卖库存集合并转MAP
     *
     * @param eventInfoDTO
     * @return
     */
    public Map<String, Integer> getFreeOrOccupyMap(WMSEventInfoDTO eventInfoDTO) {

        // 获取当前单据号销售侧库存占用：已下单已占用且未调拨申请未出可售仓
        List<StockInventoryDocOccupy> occupyList = stockInventoryDocOccupyService
                .getPlacedAndOccupyAndNoTransferNoOutDocRecord(eventInfoDTO.getDocNo());

        // 采购入库链路到可售仓之后就不处于任务链路之中
        // 当前调拨申请或出库请求无销售侧单据占用
        if (CollectionUtils.isEmpty(occupyList)) {
            isSaleOccupyLocal.set(false);
            return getFreeMap(eventInfoDTO);
        } else { // 当前调拨申请或出库请求有销售侧单据占用
            isSaleOccupyLocal.set(true);
            return getOccupyMap(eventInfoDTO);
        }
    }

    /**
     * 获取对应货品销售侧库存占用集合并转MAP：key-货品编码，value-销售占用库存数
     *
     * @param eventInfoDTO
     * @return
     */
    public Map<String, Integer> getOccupyMap(WMSEventInfoDTO eventInfoDTO) {
        //进行容错处理，针对订单号与货品编码存在重复情况
        return stockInventoryDocOccupyService.getDocOccupyRecord(eventInfoDTO.getDocNo()).stream()
                .collect(Collectors.groupingBy(StockInventoryDocOccupy::getInventoryCode,
                        Collectors.mapping(StockInventoryDocOccupy::getUsedNumAbs, Collectors.reducing(0, Integer::sum))));
    }

    /**
     * 获取对应货品空闲可动库存集合并转MAP：key-货品编码，value-销售占用库存数
     * 货品空闲可动库存数 = 货品总可售仓库数 - 货品总销售占用
     *
     * @param eventInfoDTO
     * @return
     */
    public Map<String, Integer> getFreeMap(WMSEventInfoDTO eventInfoDTO) {

        List<String> inventoryCodes = eventInfoDTO.getInventoryInfoList()
                .parallelStream().map(WMSInventoryInfo::getInventoryCode).collect(Collectors.toList());

        return stockInventoryRealService.getRealInventoryStockListIfNullCreate(inventoryCodes)
                .stream().collect(Collectors.toMap(StockInventoryReal::getInventoryCode,
                        stockReal -> stockReal.getSaleStockNum() - stockReal.getUsedStockNum()));
    }

    /**
     * 获取和当前请求互斥的调用链路货品库存占用情况MAP：key-货品编码，value-链路占用库存数
     *
     * @param eventInfoDTO
     * @param operateWarehouse
     * @return
     */
    public Map<String, Integer> getTracesMap(WMSEventInfoDTO eventInfoDTO, Boolean operateWarehouse) {

        String warehouseCode = operateWarehouse ?
                eventInfoDTO.getInWarehouseCode() : eventInfoDTO.getOutWarehouseCode();
        List<String> inventoryCodes = eventInfoDTO.getInventoryInfoList().stream()
                .map(WMSInventoryInfo::getInventoryCode).collect(Collectors.toList());

        // 当前请求无业务单号
        if (CommonUtils.isEmpty(eventInfoDTO.getDocNo())) {
            return phStockInventoryWarehouseWmsService.getAllTracesOccupyToMap(inventoryCodes, warehouseCode);
        } else { // 当前请求有业务单号
            return phStockInventoryWarehouseWmsService
                    .getAllTracesOccupyExcludeToMap(eventInfoDTO.getDocNo(), inventoryCodes, warehouseCode);
        }
    }

    /**
     * 判断当前仓库请求货品库存是否全部都符合 predicate 判断，
     * 否则抛出运行时异常阻断后续代码运行
     *
     * @param predicate
     */
    public void whStockAllMacth(Predicate<StockWarehouseRelation> predicate, StringBuffer msg) {

        ListAllMacth(whStocksLocal.get(), predicate, STOCK_WAREHOUSE_EXCEPTION_900100, msg);
    }

    /**
     * 判断当前集合中的全部元素是否全部都符合 predicate 判断，
     * 否则抛出运行时异常阻断后续代码运行
     *
     * @param predicate
     */
    public <T> void ListAllMacth(List<T> tList, Predicate<T> predicate, ResponseCodeEnum codeEnum, StringBuffer msg) {

        if (BooleanUtils.isNotTrue(tList.stream().allMatch(predicate))) {
            throw new StockRuntimeException(codeEnum, msg.toString());
        }
    }

    /**
     * 校验操作货品是否存在当前仓库，否则抛出运行时异常阻断后续代码运行
     *
     * @return
     */
    public void hasOperateInventory(WMSEventInfoDTO eventInfoDTO) {

        List<WMSInventoryInfo> infoList = eventInfoDTO.getInventoryInfoList();

        List<StockWarehouseRelation> relationList = whStocksLocal.get();
        if (CollectionUtils.isEmpty(relationList)) {
            String codeStr = infoList.stream().map(WMSInventoryInfo::getInventoryCode)
                    .collect(Collectors.joining(", "));

            List<StockInventoryReal> ifNullCreateList = stockInventoryRealService
                    .getRealInventoryStockListIfNullCreate(inventoryLocal.get().keySet());
            if (ifNullCreateList.size() < inventoryLocal.get().keySet().size())
                throw new StockRuntimeException(STOCK_WAREHOUSE_EXCEPTION_900106, codeStr);

            throw new StockRuntimeException(STOCK_WAREHOUSE_EXCEPTION_900105, codeStr);
        }

        // 出库相关
        List<ExchangeActionTypeEnum> outEnumList = Arrays.asList(
                ExchangeActionTypeEnum.SALES_RETURN_CANCLE_OUT,
                ExchangeActionTypeEnum.PURCHASE_CANCEL_OUT,
                ExchangeActionTypeEnum.PURCHASE_RETURN_OUT,
                ExchangeActionTypeEnum.CHECK_LOSS_OUT,
                ExchangeActionTypeEnum.OTHER_OUT,
                ExchangeActionTypeEnum.SALES_OUT);
        if (outEnumList.contains(eventInfoDTO.getActionType()) &&
                relationList.size() != infoList.size()) {
            List<String> codeList = relationList.stream()
                    .map(StockWarehouseRelation::getInventoryCode).collect(Collectors.toList());
            String codeStr = infoList.stream().map(WMSInventoryInfo::getInventoryCode)
                    .filter(code -> !codeList.contains(code)).collect(Collectors.joining(", "));
            codeStr += "；其它货品调拨操作成功";
            stockRuntimeExceptionLocal.set(new StockRuntimeException(STOCK_WAREHOUSE_EXCEPTION_900105, codeStr));

            Map<String, Integer> inventoryInfoMap = infoList.stream()
                    .filter(info -> codeList.contains(info.getInventoryCode()))
                    .collect(Collectors.toMap(WMSInventoryInfo::getInventoryCode,
                            WMSInventoryInfo::getInventoryQuantityAbs, Integer::sum));
            inventoryLocal.set(inventoryInfoMap);
        }
    }

    /**
     * 校验调拨申请的分批执行：调拨出库、调拨入库
     * 调拨出库请求可以分批执行，多次调拨出库的总数量不可大于调拨申请的数量
     * 调拨入库请求可以分批执行，多次调拨入库的总数量不可大于调拨申请的数量
     *
     * @param eventInfoDTO
     */
    public void checkApplyExecuted(WMSEventInfoDTO eventInfoDTO) {

        if (CollectionUtils.isEmpty(eventInfoDTO.getInventoryInfoList())) return;

        // 当前调拨出/入库关联的调拨申请的货品数量MAP：key-货品编码，value-调拨申请数量
        Map<String, Integer> applyMap = whChangesLocal.get().stream().filter(change ->
                        TRANSFER_APPLY.getCode().equals(change.getActionType()))
                .collect(Collectors.toMap(StockWarehouseChange::getInventoryCode, StockWarehouseChange::getUpdatedNumAbs));
        // 当前调拨出/入库关联的调拨申请已经执行了调拨出/入库的货品总数量MAP：key-货品编码，value-调拨出/入库数量
        Map<String, Integer> executedMap = whChangesLocal.get().stream().filter(change ->
                        eventInfoDTO.getActionType().getCode().equals(change.getActionType()))
                .collect(Collectors.groupingBy(StockWarehouseChange::getInventoryCode,
                        Collectors.mapping(StockWarehouseChange::getUpdatedNumAbs,
                                Collectors.reducing(0, Integer::sum))));

        // 多次调拨出/入库的总数量不可大于调拨申请的数量
        boolean allMatch = eventInfoDTO.getInventoryInfoList().stream().allMatch(stock -> {
            Integer applyNum = applyMap.get(stock.getInventoryCode());
            if (CommonUtils.isEmptyNum(applyNum)) return false;
            Integer executedNum = executedMap.getOrDefault(stock.getInventoryCode(), 0);
            return applyNum >= executedNum + stock.getInventoryQuantity();
        });
        if (BooleanUtils.isNotTrue(allMatch)) {
            throw new StockRuntimeException(STOCK_WAREHOUSE_EXCEPTION_900111);
        }
    }

    /**
     * 根据当前请求关联单据是否存在销售库存占用决定影响销售侧库存动态类型：重载
     *
     * @param eventInfoDTO
     * @param existEvent
     * @param nonExistEvent
     * @return
     */
    public StockChangeInnerEventEnum checkDocOccupy(WMSEventInfoDTO eventInfoDTO,
                                                    Function<String, List<StockInventoryDocOccupy>> getOccupyListFun,
                                                    StockChangeInnerEventEnum existEvent,
                                                    StockChangeInnerEventEnum nonExistEvent) {

        return checkDocOccupy(eventInfoDTO, getOccupyListFun, existEvent, nonExistEvent, null);
    }

    /**
     * 根据当前请求关联单据是否存在销售库存占用决定影响销售侧库存动态类型：重载
     *
     * @param eventInfoDTO
     * @param existEvent
     * @param nonExistExceptionCode
     * @return
     */
    public StockChangeInnerEventEnum checkDocOccupy(WMSEventInfoDTO eventInfoDTO,
                                                    Function<String, List<StockInventoryDocOccupy>> getOccupyListFun,
                                                    StockChangeInnerEventEnum existEvent,
                                                    ResponseCodeEnum nonExistExceptionCode) {

        return checkDocOccupy(eventInfoDTO, getOccupyListFun, existEvent, null, nonExistExceptionCode);
    }

    /**
     * 根据当前请求关联单据是否存在销售库存占用决定影响销售侧库存动态类型
     *
     * @param eventInfoDTO
     * @param existEvent
     * @param nonExistEvent
     * @param nonExistExceptionCode
     * @return
     */
    private StockChangeInnerEventEnum checkDocOccupy(WMSEventInfoDTO eventInfoDTO,
                                                     Function<String, List<StockInventoryDocOccupy>> getOccupyListFun,
                                                     StockChangeInnerEventEnum existEvent,
                                                     StockChangeInnerEventEnum nonExistEvent,
                                                     ResponseCodeEnum nonExistExceptionCode) {

        if (CommonUtils.isEmpty(eventInfoDTO.getDocNo())) return nonExistEvent;

        // 调拨申请、销售出库、采购出库：根据当前请求的关联单据号查询已下单为占用且已调拨申请未出可售仓
        // 取消调拨申请：根据当前请求的关联单据号查询已下单为占用且已调拨申请未出可售仓
        List<StockInventoryDocOccupy> occupyList = getOccupyListFun.apply(eventInfoDTO.getDocNo());

        if (CollectionUtils.isNotEmpty(occupyList)) { // 存在销售单据库存占用
            return existEvent;
        } else { // 不存在销售单据库存占用
            // 可售仓发生当前动作若不存在销售单据库存占用则抛出异常
            if (ObjectUtils.isNotEmpty(nonExistExceptionCode)) {
                if (nonExistExceptionCode.equals(STOCK_WAREHOUSE_EXCEPTION_900113)) {
                    List<String> inventoryCodes = eventInfoDTO.getInventoryInfoList()
                            .stream().map(WMSInventoryInfo::getInventoryCode).collect(Collectors.toList());
                    List<StockInventoryDocOccupy> docRecordList = stockInventoryDocOccupyService.
                            getDocRecordList(eventInfoDTO.getDocNo(), inventoryCodes);
                    if (CollectionUtils.isEmpty(docRecordList))
                        throw new StockRuntimeException(nonExistExceptionCode, eventInfoDTO.getDocNo() + "不存在库存占用");
                    if (docRecordList.stream().anyMatch(occupy -> occupy.getStatus() == 2))
                        throw new StockRuntimeException(nonExistExceptionCode, eventInfoDTO.getDocNo() + "已发货出库");
                    if (docRecordList.stream().anyMatch(occupy -> occupy.getStatus() == 3))
                        throw new StockRuntimeException(nonExistExceptionCode, eventInfoDTO.getDocNo() + "已取消库存占用");
                }
                throw new StockRuntimeException(nonExistExceptionCode);
            }

            return nonExistEvent;
        }
    }

}
